import * as THREE from 'three';
import TWEEN from '@tweenjs/tween.js';

import CSSManager from './Manager/CSSManager';
import Controls from './Controls';
import Light from './Light';
import { loadByFile, loadByUrl } from './Loader';
import { enableAxesHelper, enableCameraHelper, enableGridHelper, enableViewHelper } from './Helper';
import { initoutlinePassesAndComposer } from './Manager/postProcess/outline';
import { initMxintBloomPassesComposer, initMxintBloomPassesComposer_thirdparty } from './Manager/postProcess/bloom';

/**
 * TODO:
 *  - 需要特别维护objects和scene.children
 */

export default class Editor {
  /**
   * @type {THREE.Scene | null}
   */
  scene = null;

  /**
   * @type {THREE.PerspectiveCamera | null}
   */
  camera = null;

  /**
   * @type {THREE.WebGLRenderer | null}
   */
  renderer = null;

  /**
   * @type {HTMLCanvasElement | null}
   */
  canvas = null;

  /**
   * @type {Light | null}
   */
  light = null;

  /**
   * @type {Controls | null}
   */
  controls = null;

  /**
   * @type {CSSManager | null}
   */
  cssManager = null;

  objects = [];

  option = {};

  /**
   * @param {object} option
   * @param {string} option.canvasId
   * @param {string} option.containerId
   */
  constructor(option) {
    const { canvasId, containerId } = option;
    this.option = option;
    const canvas = document.getElementById(canvasId);
    if (!canvas) return;
    this.canvas = canvas;
    // 初始化场景
    const scene = this.initScene();
    this.scene = scene;
    // 初始化灯光
    this.light = new Light({ scene: this.scene });

    // 初始化相机
    const camera = this.initCamera();
    // 初始化渲染器
    const renderer = this.initRenderer();
    // 初始化辅助工具
    this.initHelper();
    // 初始化控制器
    this.controls = new Controls({ scene, camera, renderer, objects: this.objects });
    // 初始化后处理相关
    // const { composer } = initComposerAndPass(scene, camera, renderer, this.controls.transformControls, this.objects);
    // this.outlinePass = outlinePass; // 可获取到当前高亮的object
    // this.composer = composer;
    // const { outlineComposer } = initoutlinePassesAndComposer({
    //   containerId,
    //   scene,
    //   camera,
    //   renderer,
    //   transformControls: this.controls.transformControls,
    //   objects: this.objects,
    // });
    // this.outlineComposer = outlineComposer;

    const { finalComposer, bloomEffect } = initMxintBloomPassesComposer_thirdparty({ scene, renderer, camera, containerId });
    this.finalComposer = finalComposer;
    this.bloomEffect = bloomEffect;

    // 初始化CSS相关管理器
    this.cssManager = new CSSManager({ scene, camera, renderer, containerId });

    // TODO:应该支持取消监听
    // 启用resize
    window.onresize = this.resize.bind(this);
  }

  initScene() {
    const objects = this.objects;

    // 重写add方法
    const originalAdd = THREE.Scene.prototype.add;
    THREE.Scene.prototype.add = function (...args) {
      let shouldAddToCustomObjects = false;
      let objectsToAdd = args;
      if (typeof args[args.length - 1] === 'boolean') {
        shouldAddToCustomObjects = args.pop();
      }
      objectsToAdd.forEach((object) => {
        if (shouldAddToCustomObjects) {
          objects.push(object);
        }
      });
      return Reflect.apply(originalAdd, this, objectsToAdd);
    };

    // 重写remove方法
    const originalRemove = THREE.Scene.prototype.remove;
    THREE.Scene.prototype.remove = function (...args) {
      let shouldRemoveFromCustomObjects = false;
      let objectsToRemove = args;
      if (typeof args[args.length - 1] === 'boolean') {
        shouldRemoveFromCustomObjects = args.pop();
      }
      objectsToRemove.forEach((object) => {
        if (shouldRemoveFromCustomObjects) {
          const index = objects.indexOf(object);
          if (index > -1) {
            objects.splice(index, 1);
          }
        }
      });
      return Reflect.apply(originalRemove, this, objectsToRemove);
    };

    const scene = new THREE.Scene();
    return scene;
  }

  /**
   * @description 依赖scene、camera、renderer
   */
  initHelper() {
    enableAxesHelper(this.scene);
    enableGridHelper(this.scene);
    // this.cameraHelper = enableCameraHelper(this.scene, this.camera);
    // TODO:应该支持关闭
    this.viewHelper = enableViewHelper(this.camera, this.renderer.domElement);
  }
  /**
   * @returns {THREE.PerspectiveCamera}
   */
  initCamera() {
    const width = this.canvas.clientWidth;
    const height = this.canvas.clientHeight;
    const camera = new THREE.PerspectiveCamera(50, width / height, 1, 5000);
    camera.position.set(-75, 50, 100);
    // camera.position.set(100, 200, 550);
    this.camera = camera;
    return camera;
  }
  /**
   * @returns {THREE.WebGLRenderer}
   */
  initRenderer() {
    if (!this.canvas) return;
    const renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
    renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    // renderer.autoClear = false; // 启用viewHelper时需要设置
    this.renderer = renderer;
    return renderer;
  }
  /**
   * @param {object} config
   * @param {string} [config.url]
   * @param {File} [config.file]
   * @param {number} [config.scale]
   * @param {(object3D: THREE.Object3D) => void} [successCallback]
   */
  async load(config, successCallback) {
    const { url, file, scale = 0.25 } = config;
    try {
      // TODO: 策略模式重构
      let object = null;
      if (url) {
        object = await loadByUrl(url, scale);
      } else {
        object = await loadByFile(file, scale);
        console.log('埋点');
      }
      // draggable && this.objects.push(object);
      if (typeof successCallback === 'function') {
        successCallback(object);
      }
      return object;
    } catch (error) {
      console.error(error);
      return null;
    }
  }

  animate() {
    TWEEN.update();
    this.cssManager.render();
    // this.outlineComposer.render(); // 启用outline
    this.finalComposer.render(); // 启用三方BLOOM时使用
    // this.renderBloom(); // 启用原生BLOOM时使用
    // this.viewHelper.render(this.renderer);
    requestAnimationFrame(this.animate.bind(this));
  }

  resize() {
    const { containerId } = this.option;
    const box = document.getElementById(containerId);
    this.renderer.setSize(box.clientWidth, box.clientHeight);
    this.camera.aspect = box.clientWidth / box.clientHeight;
    this.camera.updateProjectionMatrix();
    // 调用CSS渲染器的resize
    this.cssManager.css2DRender.resize();
    this.cssManager.css3DRender.resize();
  }
}
